home *** CD-ROM | disk | FTP | other *** search
- Path: news.wwa.com!rmartin
- From: rmartin@oma.com (Robert C. Martin)
- Newsgroups: comp.object,comp.lang.c++
- Subject: Re: A design question
- Followup-To: comp.object,comp.lang.c++
- Date: 27 Feb 1996 15:00:05 GMT
- Organization: Object Mentor
- Message-ID: <RMARTIN.96Feb27090005@rcm.oma.com>
- References: <1996Feb22.234825.18755@dcs.warwick.ac.uk> <4gu4br$bmc@news4.digex.net>
- NNTP-Posting-Host: rcm.oma.com
- In-reply-to: ell@access1.digex.net's message of 27 Feb 1996 05:20:59 GMT
-
- In article <4gu4br$bmc@news4.digex.net> ell@access1.digex.net (Ell) writes:
-
- Robert C. Martin (rmartin@oma.com) wrote:
- : Indeed. And there is a very nice pattern in the "Design Patterns"
- : book that describes the way in which a state machine can be
- : implemented to do just what Miroslav is asking. The pattern is called
- : "State".
- :
- : Consider the following:
- :
- : class MyParcel; /fwd declare.
- :
- : // this class is the abstract representation of the state of the
- : // parcel of data. It defines functions which respond to all the
- : // events that a parcel may experience.
- :
- : class ParcelState
- : {
- : public:
- : // the events that occur along the processing chain.
- : virtual void Event1(MyParcel&) = 0;
- : virtual void Event2(MyParcel&) = 0;
- : };
- :
- : // This is the parcel itself. It also defines functions which respond
- : // to all its events. However, these functions simply delegate to the
- : // contained state object.
- :
- : class MyParcel
- : {
- : public:
- : MyParcel();
- : void Event1() {itsState->Event1(*this);}
- : void Event2() {itsState->Event2(*this);}
- : private:
- : ParcelState* itsState;
- : };
-
- I'm wondering if the above class MyParcel shouldn't publicly inherit from
- ParcelState?
-
- Actually the story is not complete. Once told it will be clear why
- deriving MyParcel from Parcel state is not a good idea.
-
- Let's change the names to make things clearer.
-
- Assume that we have a subway turnstyle. The class that represents
- this turnstyle has several public member functions:
-
- class Turnstyle
- {
- public:
- void Lock();
- void Unlock();
- void Refund();
- void Alarm();
- };
-
- These are all the operations that the turnstyle can perform. We want
- to create the logic that will cause the appropriate operation to be
- invoked in response to an event. There are two possible events:
-
- Coin - The user drops a coin or subway token into the slot.
- Pass - The user passes through the turnstyle.
-
- We can create a state table that describes the operation of the
- turnstyle.
-
- Current State Event New State Action
- LOCKED COIN UNLOCKED Unlock
- LOCKED PASS LOCKED Alarm
- UNLOCKED COIN UNLOCKED Refund
- UNLOCKED PASS LOCKED Lock
-
- Fine. Now we can create a class that derives from Turnstyle and
- implements this FSM. Note the class below: simpleTurnstyle
-
- ---------------simpleTurnstyle.h (generated by smc) ---------------
- #ifndef _H_SimpleTurnstyle
- #define _H_SimpleTurnstyle
- #include <stddef.h>
- #include "turnstyle.h"
- class SimpleTurnstyle;
-
- class SimpleTurnstyleState {
- public:
-
- virtual const char* StateName() const = 0;
- virtual void PASS(SimpleTurnstyle& s);
- virtual void COIN(SimpleTurnstyle& s);
- };
-
- class SimpleTurnstyleUNLOCKEDState : public SimpleTurnstyleState {
- public:
- virtual const char* StateName() const
- {return("UNLOCKED");};
- virtual void PASS(SimpleTurnstyle&);
- virtual void COIN(SimpleTurnstyle&);
- };
-
- class SimpleTurnstyleLOCKEDState : public SimpleTurnstyleState {
- public:
- virtual const char* StateName() const
- {return("LOCKED");};
- virtual void PASS(SimpleTurnstyle&);
- virtual void COIN(SimpleTurnstyle&);
- };
- class SimpleTurnstyle : public Turnstyle {
- public:
- static SimpleTurnstyleUNLOCKEDState UNLOCKEDState;
- static SimpleTurnstyleLOCKEDState LOCKEDState;
- SimpleTurnstyle();// default constructor
- void PASS() {itsState->PASS(*this);}
- void COIN() {itsState->COIN(*this);}
- void SetState(SimpleTurnstyleState& theState) {itsState=&theState;}
- SimpleTurnstyleState& GetState() const {return *itsState;};
- private:
- SimpleTurnstyleState* itsState;
- };
- #endif
-
- -------------simpleTurnstyle.cc------------------
-
-
- #include "simpleTurnstyle.h"
- static char _versID[] = "No Version.";
- SimpleTurnstyleUNLOCKEDState SimpleTurnstyle::UNLOCKEDState;
- SimpleTurnstyleLOCKEDState SimpleTurnstyle::LOCKEDState;
- void SimpleTurnstyleState::PASS(SimpleTurnstyle& s)
- {s.FSMError("PASS", s.GetState().StateName());}
- void SimpleTurnstyleState::COIN(SimpleTurnstyle& s)
- {s.FSMError("COIN", s.GetState().StateName());}
- void SimpleTurnstyleUNLOCKEDState::PASS(SimpleTurnstyle& s) {
- s.SetState(SimpleTurnstyle::LOCKEDState);
- s.Lock();
- }
- void SimpleTurnstyleUNLOCKEDState::COIN(SimpleTurnstyle& s) {
- s.SetState(SimpleTurnstyle::UNLOCKEDState);
- s.Refund();
- }
- void SimpleTurnstyleLOCKEDState::PASS(SimpleTurnstyle& s) {
- s.SetState(SimpleTurnstyle::LOCKEDState);
- s.Alarm();
- }
- void SimpleTurnstyleLOCKEDState::COIN(SimpleTurnstyle& s) {
- s.SetState(SimpleTurnstyle::UNLOCKEDState);
- s.Unlock();
- }
- SimpleTurnstyle::SimpleTurnstyle() : itsState(&LOCKEDState) {}
-
- -----------------------------------------------------------
-
- OK, now it looks like we could have simply put the PASS and COIN
- events into Turnstyle and then had a common base for Turnstyle and
- simpleTurnstyleState. However, consider this:
-
-
- LOCKED COIN HALFPAID {}
- LOCKED PASS VIOLATED {Alarm Lock}
- HALFPAID COIN UNLOCKED Unlock
- HALFPAID PASS VIOLATED {Alarm Lock}
- UNLOCKED COIN UNLOCKED Refund
- UNLOCKED PASS LOCKED Lock
- VIOLATED COIN VIOLATED Refund
- VIOLATED PASS VIOLATED {Alarm Lock}
- VIOLATED RESET LOCKED {}
-
- Here is some turnstyle logic that is much more complicated than the
- previous one. This logic requires TWO coins before unlocking.
- Moreover, once violated, it refuses to operate until a maintenance
- worker issues the RESET event.
-
- The code for this TwoCoinTurnstyle is shown below. Note that the
- TwoCoinTurnstyle class derives from Turnstyle.
-
- -------------------twoCoinTurnstyle.h (generated by smc) --------
-
- #ifndef _H_TwoCoinTurnstyle
- #define _H_TwoCoinTurnstyle
- #include <stddef.h>
- #include "turnstyle.h"
- class TwoCoinTurnstyle;
-
- class TwoCoinTurnstyleState {
- public:
-
- virtual const char* StateName() const = 0;
- virtual void RESET(TwoCoinTurnstyle& s);
- virtual void PASS(TwoCoinTurnstyle& s);
- virtual void COIN(TwoCoinTurnstyle& s);
- };
-
- class TwoCoinTurnstyleVIOLATEDState : public TwoCoinTurnstyleState {
- public:
- virtual const char* StateName() const
- {return("VIOLATED");};
- virtual void RESET(TwoCoinTurnstyle&);
- virtual void PASS(TwoCoinTurnstyle&);
- virtual void COIN(TwoCoinTurnstyle&);
- };
-
- class TwoCoinTurnstyleUNLOCKEDState : public TwoCoinTurnstyleState {
- public:
- virtual const char* StateName() const
- {return("UNLOCKED");};
- virtual void PASS(TwoCoinTurnstyle&);
- virtual void COIN(TwoCoinTurnstyle&);
- };
-
- class TwoCoinTurnstyleHALFPAIDState : public TwoCoinTurnstyleState {
- public:
- virtual const char* StateName() const
- {return("HALFPAID");};
- virtual void PASS(TwoCoinTurnstyle&);
- virtual void COIN(TwoCoinTurnstyle&);
- };
-
- class TwoCoinTurnstyleLOCKEDState : public TwoCoinTurnstyleState {
- public:
- virtual const char* StateName() const
- {return("LOCKED");};
- virtual void PASS(TwoCoinTurnstyle&);
- virtual void COIN(TwoCoinTurnstyle&);
- };
- class TwoCoinTurnstyle : public Turnstyle {
- public:
- static TwoCoinTurnstyleUNLOCKEDState UNLOCKEDState;
- static TwoCoinTurnstyleVIOLATEDState VIOLATEDState;
- static TwoCoinTurnstyleHALFPAIDState HALFPAIDState;
- static TwoCoinTurnstyleLOCKEDState LOCKEDState;
- TwoCoinTurnstyle();// default constructor
- void RESET() {itsState->RESET(*this);}
- void PASS() {itsState->PASS(*this);}
- void COIN() {itsState->COIN(*this);}
- void SetState(TwoCoinTurnstyleState& theState) {itsState=&theState;}
- TwoCoinTurnstyleState& GetState() const {return *itsState;};
- private:
- TwoCoinTurnstyleState* itsState;
- };
- #endif
-
- -------------------twoCoinTurnstyle.cc (generated by smc) -----
-
- #include "twoCoinTurnstyle.h"
- static char _versID[] = "No Version.";
- TwoCoinTurnstyleUNLOCKEDState TwoCoinTurnstyle::UNLOCKEDState;
- TwoCoinTurnstyleVIOLATEDState TwoCoinTurnstyle::VIOLATEDState;
- TwoCoinTurnstyleHALFPAIDState TwoCoinTurnstyle::HALFPAIDState;
- TwoCoinTurnstyleLOCKEDState TwoCoinTurnstyle::LOCKEDState;
- void TwoCoinTurnstyleState::RESET(TwoCoinTurnstyle& s)
- {s.FSMError("RESET", s.GetState().StateName());}
- void TwoCoinTurnstyleState::PASS(TwoCoinTurnstyle& s)
- {s.FSMError("PASS", s.GetState().StateName());}
- void TwoCoinTurnstyleState::COIN(TwoCoinTurnstyle& s)
- {s.FSMError("COIN", s.GetState().StateName());}
- void TwoCoinTurnstyleVIOLATEDState::RESET(TwoCoinTurnstyle& s) {
- s.SetState(TwoCoinTurnstyle::LOCKEDState);
- }
- void TwoCoinTurnstyleVIOLATEDState::PASS(TwoCoinTurnstyle& s) {
- s.SetState(TwoCoinTurnstyle::VIOLATEDState);
- s.Alarm();
- s.Lock();
- }
- void TwoCoinTurnstyleVIOLATEDState::COIN(TwoCoinTurnstyle& s) {
- s.SetState(TwoCoinTurnstyle::VIOLATEDState);
- s.Refund();
- }
- void TwoCoinTurnstyleUNLOCKEDState::PASS(TwoCoinTurnstyle& s) {
- s.SetState(TwoCoinTurnstyle::LOCKEDState);
- s.Lock();
- }
- void TwoCoinTurnstyleUNLOCKEDState::COIN(TwoCoinTurnstyle& s) {
- s.SetState(TwoCoinTurnstyle::UNLOCKEDState);
- s.Refund();
- }
- void TwoCoinTurnstyleHALFPAIDState::PASS(TwoCoinTurnstyle& s) {
- s.SetState(TwoCoinTurnstyle::VIOLATEDState);
- s.Alarm();
- s.Lock();
- }
- void TwoCoinTurnstyleHALFPAIDState::COIN(TwoCoinTurnstyle& s) {
- s.SetState(TwoCoinTurnstyle::UNLOCKEDState);
- s.Unlock();
- }
- void TwoCoinTurnstyleLOCKEDState::PASS(TwoCoinTurnstyle& s) {
- s.SetState(TwoCoinTurnstyle::VIOLATEDState);
- s.Alarm();
- s.Lock();
- }
- void TwoCoinTurnstyleLOCKEDState::COIN(TwoCoinTurnstyle& s) {
- s.SetState(TwoCoinTurnstyle::HALFPAIDState);
- }
- TwoCoinTurnstyle::TwoCoinTurnstyle() : itsState(&LOCKEDState) {}
-
- -------------------------------------------------------------------
-
- From this you can see the reason that Turnstyle cannot derive from
- TurnstyleState. We want to be able to reuse Turnstyle (the primitive
- operations) in many different finite state machines. So we want to
- isolate the events of the FSM from the actions of the FSM.
-
- --
- Robert Martin | Design Consulting | Training courses offered:
- Object Mentor Assoc.| rmartin@oma.com | OOA/D, C++, Advanced OO
- 14619 N. Somerset Cr| Tel: (847) 918-1004 | Mgt. Overview of OOT
- Green Oaks IL 60048 | Fax: (847) 918-1023 | Development Contracts.
-
-